package measures;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

import utilities.DCUtilities;
import utilities.Logger;

import classification.CategoryComparator;

import model.Category;
import model.Document;

public class Measures {
	private List<Document> classified;
	private HashMap<String, Category> measuredCategories;
	private double averagePrecision;
	private double averageRecall;
	
	private double weightAveragePrecision;
	private double weightAverageRecall;
	
	private double weightAveragePrecisionTrain;
	private double weightAverageRecallTrain;
	
	public Measures (List<Document> classified){
		this.classified = classified;
		measuredCategories = new HashMap<String, Category>();
	}
	
	public void measure () {
		measuredCategories.clear();
		averagePrecision = 0;
		averageRecall = 0;
		weightAveragePrecision = 0;
		weightAverageRecall = 0;
		weightAveragePrecisionTrain = 0;
		weightAverageRecallTrain = 0;
		
		for (Document doc:classified){
			for (Category cat:doc.getCategories().values()){
				measuredCategories.put(cat.getCategoryName(), cat);
			}
			for (Category cat:doc.getNewCategories().values()){
				measuredCategories.put(cat.getCategoryName(), cat);
			}
		}
		double precisionSum = 0.0;
		double weightPrecisionSum = 0.0;
		double weightPrecisionSumTrain = 0.0;
		int precisionCount = 0;
		int precisionWeight = 0;
		int precisionWeightTrain = 0;
		double recallSum = 0.0;
		double weightRecallSum = 0.0;
		double weightRecallSumTrain = 0.0;
		int recallCount = 0;
		int recallWeight = 0;
		int recallWeightTrain = 0;
		for (Category cat:measuredCategories.values()){
			int truePositives = 0;
			int positives = 0;
			int allHaving = 0;
			for (Document doc:classified){
				if (doc.hasCategory(cat)){
					allHaving ++;
				}
				if (doc.hasNewCategory(cat)){
					positives++;
				}
				if (doc.hasCategory(cat) && doc.hasNewCategory(cat)){
					truePositives ++;
				}
			}
			cat.setAllHavingInTestSet(allHaving);
			if (positives > 0){
				double prec = ((double)truePositives)/((double)positives);
				cat.setPrecision(prec);
				precisionSum += prec;
				weightPrecisionSum += prec * allHaving;
				weightPrecisionSumTrain += prec * cat.getCategoryDocs().size();
				precisionCount ++;
				precisionWeight += allHaving;
				precisionWeightTrain += cat.getCategoryDocs().size();
			} else {
				cat.setPrecision(-1.0);
			}
			if (allHaving > 0){
				double rec = ((double)truePositives)/((double)allHaving);
				cat.setRecall(rec);
				recallSum += rec;
				weightRecallSum += rec * allHaving;
				weightRecallSumTrain += rec * cat.getCategoryDocs().size();
				recallCount ++;
				recallWeight += allHaving;
				recallWeightTrain += cat.getCategoryDocs().size();
			} else {
				cat.setRecall(-1.0);
			}
		}
		averagePrecision = precisionSum / ((double)precisionCount);
		averageRecall = recallSum / ((double)recallCount);
		
		weightAveragePrecision = weightPrecisionSum / ((double)precisionWeight);
		weightAverageRecall = weightRecallSum / ((double)recallWeight);
		
		weightAveragePrecisionTrain = weightPrecisionSumTrain / ((double)precisionWeightTrain);
		weightAverageRecallTrain = weightRecallSumTrain / ((double)recallWeightTrain);
	}
	
	public void printStatistics(){
		Logger.logln("=== STATISTICS ===", DCUtilities.STATISTICS_OUTPUT_FILE_KEY);
		List<Category> categories = new ArrayList<Category>(measuredCategories.values());
		Collections.sort(categories, new CategoryNameCompare());
		for (Category cat:categories){
			Logger.logln("Category: "+cat.getCategoryName(), DCUtilities.STATISTICS_OUTPUT_FILE_KEY);
			Logger.logln("all documents in test set assigned to this category = "+cat.getAllHavingInTestSet(), DCUtilities.STATISTICS_OUTPUT_FILE_KEY);
			Logger.logln("all documents in training set assigned to this category = "+cat.getCategoryDocs().size(), DCUtilities.STATISTICS_OUTPUT_FILE_KEY);
			double prec = cat.getPrecision();
			if (prec < 0){
				Logger.logln("\tPrecision:\tUNDEFINED", DCUtilities.STATISTICS_OUTPUT_FILE_KEY);
			} else {
				Logger.logln("\tPrecision:\t"+prec, DCUtilities.STATISTICS_OUTPUT_FILE_KEY);
			}
			double rec = cat.getRecall();
			if (rec < 0){
				Logger.logln("\tRecall:\tUNDEFINED", DCUtilities.STATISTICS_OUTPUT_FILE_KEY);
			} else {
				Logger.logln("\tRecall:\t\t"+rec, DCUtilities.STATISTICS_OUTPUT_FILE_KEY);
			}
		}
		Logger.logln("=== AVERAGE ===", DCUtilities.STATISTICS_OUTPUT_FILE_KEY);
		Logger.logln("|| Precision:\t"+averagePrecision, DCUtilities.STATISTICS_OUTPUT_FILE_KEY);
		Logger.logln("|| Recall:\t"+averageRecall, DCUtilities.STATISTICS_OUTPUT_FILE_KEY);
		Logger.logln("===============", DCUtilities.STATISTICS_OUTPUT_FILE_KEY);
		
		Logger.logln("=== WEIGHT AVERAGE (acording to documents having category in test set) ===", DCUtilities.STATISTICS_OUTPUT_FILE_KEY);
		Logger.logln("|| Precision:\t"+weightAveragePrecision, DCUtilities.STATISTICS_OUTPUT_FILE_KEY);
		Logger.logln("|| Recall:\t"+weightAverageRecall, DCUtilities.STATISTICS_OUTPUT_FILE_KEY);
		Logger.logln("===============", DCUtilities.STATISTICS_OUTPUT_FILE_KEY);
		
	}
}
